package com.gorkr.cronback.pattern;

import cn.hutool.core.date.DateUnit;
import com.gorkr.cronback.pattern.designPattern.adapter.LongTime;
import com.gorkr.cronback.pattern.designPattern.factory.ValueMatcherFactory;
import com.gorkr.cronback.pattern.designPattern.proxy.CronPatternInterface;
import com.gorkr.cronback.pattern.matcher.BoolArrayValueMatcher;
import com.gorkr.cronback.pattern.matcher.Matcher;
import com.gorkr.cronback.pattern.parser.*;

import java.util.*;

/**
 * 定时任务表达式<br>
 * 表达式类似于Linux的crontab表达式，表达式使用空格分成5个部分，按顺序依次为：
 * <ol>
 * <li><strong>分</strong> ：范围：0~59</li>
 * <li><strong>时</strong> ：范围：0~23</li>
 * <li><strong>日</strong> ：范围：1~31，<strong>"L"</strong> 表示月的最后一天</li>
 * <li><strong>月</strong> ：范围：1~12，同时支持不区分大小写的别名："jan","feb", "mar", "apr", "may","jun", "jul", "aug", "sep","oct", "nov", "dec"</li>
 * <li><strong>周</strong> ：范围：0 (Sunday)~6(Saturday)，7也可以表示周日，同时支持不区分大小写的别名："sun","mon", "tue", "wed", "thu","fri", "sat"，<strong>"L"</strong> 表示周六</li>
 * </ol>
 *
 * 为了兼容Quartz表达式，同时支持6位和7位表达式，其中：<br>
 *
 * <pre>
 * 当为6位时，第一位表示<strong>秒</strong> ，范围0~59，但是第一位不做匹配
 * 当为7位时，最后一位表示<strong>年</strong> ，范围1970~2099，但是第7位不做解析，也不做匹配
 * </pre>
 *
 * 当定时任务运行到的时间匹配这些表达式后，任务被启动。<br>
 * 注意：
 *
 * <pre>
 * 当isMatchSecond为{@code true}时才会匹配秒部分
 * 默认都是关闭的
 * </pre>
 *
 * 对于每一个子表达式，同样支持以下形式：
 * <ul>
 * <li><strong>*</strog> ：n表示匹配这个位置所有的时间</li>
 * <li><strong>?</strong> ：表示匹配这个位置任意的时间（与"*"作用一致）</li>
 * <li><strong>*&#47;2</strong> ：表示间隔时间，例如在分上，表示每两分钟，同样*可以使用数字列表代替，逗号分隔</li>
 * <li><strong>2-8</strong> ：表示连续区间，例如在分上，表示2,3,4,5,6,7,8分</li>
 * <li><strong>2,3,5,8</strong> ：表示列表</li>
 * </ul>
 * 注意：在每一个子表达式中优先级：
 *
 * <pre>
 * 间隔（/） &gt; 区间（-） &gt; 列表（,）
 * </pre>
 *
 * 例如 2,3,6/3中，由于“/”优先级高，因此相当于2,3,(6/3)，结果与 2,3,6等价<br>
 * <br>
 *
 * 一些例子：
 * <ul>
 * <li><strong>5 * * * *</strong> ：每个点钟的5分执行，00:05,01:05……</li>
 * <li><strong>* * * * *</strong> ：每分钟执行</li>
 * <li><strong>*&#47;2 * * * *</strong> ：每两分钟执行</li>
 * <li><strong>* 12 * * *</strong> ：12点的每分钟执行</li>
 * <li><strong>59 11 * * 1,2</strong> ：每周一和周二的11:59执行</li>
 * <li><strong>3-18&#47;5 * * * *</strong> ：3~18分，每5分钟执行一次，即0:03, 0:08, 0:13, 0:18, 1:03, 1:08……</li>
 * </ul>
 *
 */
// 先支持5位
public class CronPattern implements CronPatternInterface {
    private ValueMatcherFactory valueMatcherFactory;



    private final String pattern;

    private static final ValueParser minuteValueParser = new MinuteValueParser();
    private static final ValueParser hourValueParser = new HourValueParser();
    private static final ValueParser dayOfMonthParser = new DayOfMonthParser();
    private static final ValueParser monthValueParser = new MonthValueParser();
    private static final ValueParser dayOfWeekParser = new DayOfWeekParser();


    private Matcher minuteMatcher;
    private Matcher hourMatcher;
    private Matcher dayOfMonthValueMatcher;
    private Matcher monthValueMatcher;
    private Matcher dayOfWeekMatcher;

    public CronPattern(String pattern, ValueMatcherFactory valueMatcherFactory) throws Exception {
        this.pattern = pattern;
        this.valueMatcherFactory = valueMatcherFactory;

        // 每次创建都执行这个 ， 可以使用代理模式 仅在第一次创建时执行， 节省开销
        // CronPattern里的公开的接口太多了，容易为恶意调用，影响系统安全性。可以设置代理，仅暴露match接口
        System.out.println("执行parsePattern");
        parsePattern(pattern);
    }


    private void parsePattern(String pattern) throws Exception {

        final String[] parts = pattern.split("\\s"); // 匹配任意空格

        // 分
        minuteMatcher = valueMatcherFactory.buildMatcher(parts[0],minuteValueParser);

        // 时
        hourMatcher = valueMatcherFactory.buildMatcher(parts[1],hourValueParser);

        // 天
        dayOfMonthValueMatcher = valueMatcherFactory.buildMatcher(parts[2],dayOfMonthParser);

        // 月
        monthValueMatcher = valueMatcherFactory.buildMatcher(parts[3],monthValueParser);

        // 周
         dayOfWeekMatcher = valueMatcherFactory.buildMatcher(parts[4],dayOfWeekParser);
    }

    /**
     * 给定时间是否匹配定时任务表达式
     *
     * @param calendar 时间
     * @return 如果匹配返回 {@code true}, 否则返回 {@code false}
     */
    @Override
    public boolean match(Calendar calendar){
        final int minute = calendar.get(Calendar.MINUTE);
        final int hour = calendar.get(Calendar.HOUR_OF_DAY);
        final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH) ; // 月份从1开始， calendar的枚举月份是从0开始额
        final int month = calendar.get(Calendar.MONTH) + 1;
        final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) -1;

        boolean eval;
        eval = minuteMatcher.match(minute) && hourMatcher.match(hour) && dayOfMonthValueMatcher.match(dayOfMonth)
                && monthValueMatcher.match(month) && dayOfWeekMatcher.match(dayOfWeek);
        return eval;
    }


    /**
     * 列举指定日期范围内所有匹配表达式的日期
     * @param start 起始时间
     * @param end 结束时间
     * @param count 枚举数量
     * @return 日期列表
     */
    public List<Date> matchedDates(long start, long end, int count){
        final List<Date> result = new ArrayList<>(count);
        long step = DateUnit.MINUTE.getMillis();  // 日期迭代步长
        for (long i = start; i<end; i+=step){
            final GregorianCalendar calendar = new GregorianCalendar();
            calendar.setTimeInMillis(i);
            if(match(calendar)){
                result.add(calendar.getTime());
                if(result.size() >= count){
                    break;
                }
            }
        }
        return result;
    }

    public List<Date> matchedDates(LongTime time, int count){

        // 需要用到time.get() 我们拥有Date
        final List<Date> result = new ArrayList<>(count);
        long step = DateUnit.MINUTE.getMillis();  // 日期迭代步长
        for (long i = time.getStart(); i<time.getEnd(); i+=step){
            final GregorianCalendar calendar = new GregorianCalendar();
            calendar.setTimeInMillis(i);
            if(match(calendar)){
                result.add(calendar.getTime());
                if(result.size() >= count){
                    break;
                }
            }
        }
        return result;
    }


}


// 我们有long类型的参数  但我们需要Date类型的参数 我们需要一个适配器完成 Date到long的转换